<?php
// +----------------------------------------------------------------------
// | Bwsaas
// +----------------------------------------------------------------------
// | Copyright (c) 2015~2020 http://www.buwangyun.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Gitee ( https://gitee.com/buwangyun/bwsaas )
// +----------------------------------------------------------------------
// | Author: buwangyun <hnlg666@163.com>
// +----------------------------------------------------------------------
// | Date: 2020-9-28 10:55:00
// +----------------------------------------------------------------------

namespace buwang\service;

use app\common\model\member\Wallet;
use app\common\model\MemberMiniapp;
use app\common\model\MemberMiniappOrder;
use app\common\model\Miniapp;
use app\common\model\MiniappModule;
use app\manage\model\AuthGroup;
use app\manage\model\AuthGroupAccess;
use app\manage\model\AuthGroupNode;
use app\manage\model\AuthNode;
use buwang\util\File;
use buwang\util\Sql;
use think\Exception;
use think\facade\Db;
use app\manage\model\Member;
use app\manage\model\Config;
use app\manage\model\ConfigTab;
use app\manage\model\ConfigGroup;
use app\manage\model\ConfigGroupData;

/**
 * 应用服务类
 * Class PluginService
 * @package buwang\service
 */
class MiniappService
{
    /**
     * 安装应用
     * @param string $dir 应用目录
     * @param bool $importDemoData 是否导入测试数据
     * @return bool
     * @throws Exception
     */
    public static function install(string $dir, bool $importDemoData = false)
    {
        if (empty($dir)) throw new Exception('应用目录不能为空');

        //定义一个常量，安装应用的目录
        define('MINIAPP_DIR', $dir);

        $version = self::getVersion($dir);
        if (empty($version)) throw new Exception('未找到应用配置');

        //设置应用数据表前缀
        set_miniapp_database_prefix($dir);

        //节点文件group.php是否完整
        $group_php_path = base_path($dir) . DS . "install" . DS . "group.php";
        if (!file_exists($group_php_path)) throw new Exception('install/group.php文件不存在');
        $group_php_data = include $group_php_path;
        if (empty($group_php_data)) throw new Exception('install/group.php文件不能为空');
        $hasBaseModule = 0;
        foreach ($group_php_data as $item) {
            $item['module_type'] == 1 && $hasBaseModule++;
        }
        if (1 !== $hasBaseModule) throw new Exception('应用必须有且仅有一个基础功能');

        $app = Miniapp::column('dir');
        if (in_array($dir, $app)) throw new Exception('应用已安装,禁止重复安装');

        //插入一条数据
        $data = [
            'dir' => $dir,
            'types' => $version['types'],
            'is_manage' => $version['is_manage'],
            'is_openapp' => $version['is_openapp'],
            'title' => $version['title'],
            'version' => $version['version'],
            'is_wechat_pay' => $version['is_wechat_pay'],
            'is_alipay_pay' => $version['is_alipay_pay'],
            'describe' => $version['describe'],
            'content' => $version['describe'],
            'template_id' => 0,
            'sell_price' => 0,
            'market_price' => 0,
            'expire_day' => 0,
            'logo_image' => '/static/' . strtolower($dir) . '/logo.png',
            'qrcode_image' => '',
            'style_images' => '/static/' . strtolower($dir) . '/style.png',
        ];
        //TODO 参数验证
        /*$validate = $this->validate($data,'miniapp.add');
        if(true !== $validate){
            return json(['code'=>0,'msg'=>$validate]);
        }*/

        try {
            //2.应用表增加数据
            $app = Miniapp::create($data);
            //3.应用自定义数据表删除
            self::runSQL($dir, 'install', 'install');
            //4.系统表打入数据
            //角色表 应用功能表
            self::importGroupData($dir);
            //节点表
//            self::importNodeData($dir);
            //5.是否安装测试数据
            if ($importDemoData) self::runSQL($dir, 'install', 'demo');
            //调用安装事件
            event('MiniappInstall', [$app]);
            //1.拷贝静态资源文件 app/demoApp/install/static/ => public/static/demoApp/
            if (file_exists(root_path() . 'app/' . $dir . DS . 'install' . DS . 'static' . DS)) {
                //拷贝模板到前台模板目录中去 public/static/demoApp/
                File::copy_dir(root_path() . 'app/' . $dir . DS . 'install' . DS . 'static' . DS, root_path() . 'public' . DS . 'static' . DS . strtolower($dir) . DS);
            }


        } catch (Exception $e) {
            throw new Exception($e);
        }

        return true;
    }

    /**
     * 卸载应用
     * @param string $dir 应用目录
     * @param bool $importDemoData 是否导入测试数据
     * @return bool
     * @throws Exception
     */
    public static function uninstall(string $dir)
    {
        if (empty($dir)) throw new Exception('应用目录不能为空');

        $app = Miniapp::where('dir', $dir)->find();
        if (!$app) throw new Exception('应用未安装');

        //应用已被购买  则无法卸载  只能禁用
        if (MemberMiniapp::where('miniapp_id', $app['id'])->find()) throw new Exception('应用已售出,无法卸载');

        try {
            //删除静态资源 public/static/demoApp/
            if (file_exists(root_path() . 'public' . DS . 'static' . DS . $dir . DS)) {
                //拷贝模板到前台模板目录中去 public/static/demoApp/
                File::del_dir(root_path() . 'public' . DS . 'static' . DS . $dir . DS);
            }

            $groups_str = implode(',', MiniappModule::where('miniapp_id', $app['id'])->column('group_id'));
            //节点中间表删除
            AuthGroupNode::where('group_id', 'in', $groups_str)->delete();
            //节点表删除
            AuthNode::where('app_name', $dir)->delete();
            //应用功能表删除
            MiniappModule::where('miniapp_id', $app['id'])->delete();
            //角色表删除
            AuthGroup::where('id', 'in', $groups_str)->delete();
            //应用自定义数据表删除
            self::runSQL($dir, 'uninstall', 'uninstall');
            //应用表记录删除
            $app->delete();
        } catch (Exception $e) {
            throw new Exception($e);
        }

        return true;
    }

    /**
     * 购买应用功能
     * @param int $user_id 租户id
     * @param int $miniapp_module_id 应用功能id
     * @param string $miniapp_name_diy 自定义应用名
     * @param bool $money_change 是否扣除资金，可用来判断购买来源 true:从应用市场购买 false:从插件市场购买
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public static function buy($user_id, $miniapp_module_id, $miniapp_name_diy = '', $money_change = true)
    {
        //NOTE 功能是否存在
        $module = MiniappModule::find($miniapp_module_id);
        if (!$module) throw new Exception('功能不存在');
        //NOTE 如果功能不是基础功能  查看基础功能是否已购买
        if ($module['type'] === 2) {
            $baseModule = MiniappModule::where('miniapp_id', $module['miniapp_id'])->where('type', 1)->find();
            if (!$baseModule) throw new Exception('基础功能不存在');
            if (!MemberMiniappOrder::where(['member_id' => $user_id, 'miniapp_id' => $module['miniapp_id'], 'miniapp_module_id' => $baseModule['id'], 'miniapp_module_type' => 1])->find()) throw new Exception('请先购买基础功能');
        } elseif ($module['type'] === 3 && $money_change) throw new Exception('插件功能请前往插件市场购买');

        //功能已购买
        if (MemberMiniappOrder::where(['member_id' => $user_id, 'miniapp_id' => $module['miniapp_id'], 'miniapp_module_id' => $miniapp_module_id])->find()) throw new Exception('你已购买该功能');

        //查找应用
        $miniapp = \app\common\model\Miniapp::where(['id' => $module['miniapp_id'], 'status' => 1])->field('id,dir,title,sell_price,template_id,version')->find();
        //应用不存在
        if (!$miniapp) throw new Exception('应用不存在或已关闭购买通道');

        //NOTE 账户余额
        //如果价格<=0，就不在查询数据库
        if ($money_change && $module['sell_price'] > 0 && $module['sell_price'] > Wallet::getMoney($user_id)) throw new Exception('余额不足');

        try {
            //NOTE 扣余额
            $money_change && $module['price'] > 0 && Wallet::changeMoney($user_id, 'miniapp', 0 - $module['price'], '购买应用功能');

            //NOTE 增加应用购买订单记录
            $orderParam = [
                'member_id' => $user_id,
                'miniapp_id' => $miniapp['id'],
                'miniapp_module_id' => $miniapp_module_id,
                'title' => $miniapp['title'],
                'update_var' => $miniapp['template_id'],
                'price' => $module['price'],
                'valid_days' => $module['valid_days'],
                'expire_time' => $module['valid_days'] > 0 ? time() + $module['valid_days'] * 24 * 60 * 60 : 0,
                'miniapp_module_type' => $module['type'],
            ];
            $order = MemberMiniappOrder::create($orderParam);

            //NOTE 增加用户应用表记录
            if ($module['type'] === 1) {
                $server_id = md5($order['id']);
                $memberMiniappParam = [
                    'service_id' => $server_id,
                    'miniapp_order_id' => $order['id'],
                    'member_id' => $user_id,
                    'miniapp_id' => $miniapp['id'],
                    'appname' => $miniapp_name_diy ?: $miniapp['title'],
                    'template_id' => $miniapp['template_id'],
                    'version' => $miniapp['version']
                ];
                $memberMiniapp = MemberMiniapp::create($memberMiniappParam);
            } else $memberMiniapp = MemberMiniapp::where(['member_id' => $user_id, 'miniapp_id' => $miniapp['id']])->find();

            //NOTE 菜单增加 添加角色组
            $groupAccessParam[] = [
                'uid' => $user_id,
                'group_id' => $module['group_id'],
                'name' => $module['name'],
                'scopes' => 'member'
            ];
            if (!empty($groupAccessParam)) {
                //TODO:购买应用角色删除再添加防止冲突
                AuthGroupAccess::where('uid','=',$user_id)->where('group_id','=',$module['group_id'])->where('scopes','=','member')->delete();
                $authGroupAccess = new AuthGroupAccess();
                $authGroupAccess->saveAll($groupAccessParam);
            }
            //设置一下应用数据表前缀
            set_miniapp_database_prefix($miniapp['dir']);
            //调用购买事件
            event('MiniappBuySuccess', [$user_id, $miniapp, $module, $memberMiniapp]);
        } catch (\Throwable $e) {
            throw new Exception($e->getMessage());
        }
    }

    /**
     * 获取应用配置信息
     * @param string $dir
     * @return array|mixed
     */
    public static function getVersion(string $dir)
    {
        $miniapp_config = root_path() . 'app/' . $dir . '/config/version.php';
        $config = [];
        if (is_file($miniapp_config)) {
            $config = include $miniapp_config;
        }
        return $config;
    }

    /**
     * 执行数据库脚本
     * @param string $name 应用目录
     * @param string $dir 数据库脚本所在目录,默认install
     * @param string $sqlName 数据库脚本名称,默认install
     * @return bool
     * @throws Exception
     */
    public static function runSQL(string $name = '', string $dir = 'install', string $sqlName = 'install')
    {
        //数据库安装脚本文件地址 app/demoApp/install/install.sql
        //数据库卸载脚本文件地址 app/demoApp/uninstall/uninstall.sql
        $sql_file = base_path($name) . DS . "{$dir}" . DS . "{$sqlName}.sql";
        if (file_exists($sql_file)) {
            $sql_statement = Sql::getSqlFromFile($sql_file);
            if (!empty($sql_statement)) {
                //修改表前缀
                $table_prefix = config('database.connections.mysql.prefix');
                if (!empty($table_prefix)) {
                    $sql_statement = str_replace('bw_', $table_prefix, $sql_statement);
                }

                foreach ($sql_statement as $value) {
                    try {
                        Db::execute($value);
                    } catch (\Exception $e) {
                        throw new Exception($e->getMessage());
                    }
                }
            }
        }
        return true;
    }

    /**
     * @param string $dir 路径
     * @param array $except 排除项
     * @return array
     * 搜索给定地址下目录列表
     */
    public static function getDir(string $dir, array $except)
    {
        $dirArray[] = NULL;
        if (false != ($handle = opendir($dir))) {
            $i = 0;
            while (false !== ($file = readdir($handle))) {
                //去掉"“.”、“..”以及带“.xxx”后缀的文件
                if (array_search($file, $except) === false && $file != ".htaccess" && $file != "." && $file != ".." && !strpos($file, ".")) {
                    $dirArray[$i] = $file;
                    $i++;
                }
            }
            //关闭句柄
            closedir($handle);
        }
        return $dirArray;
    }

    /**
     * 导入角色数据
     * @param string $name
     * @return bool
     */
    private static function importGroupData(string $name)
    {
        //检查是否有group.php
        $path = base_path($name) . DS . "install" . DS . "group.php";
        if (!file_exists($path)) return true;
        $data = include $path;
        if (empty($data)) return true;

        //获取所有应用
        $miniapps = array_flip(Miniapp::column('dir', 'id'));

        //待插入应用功能
        $modules = [];
        foreach ($data as $item) {
            //TODO 参数验证

            //角色记录
            $groupParam = [
                //角色名称,展示在角色列表中
                'name' => $item['name'],
                //角色唯一标识,不可重复
                'group_name' => $name . '_' . $item['group_name'],
                //角色备注
                'remark' => $item['remark'] ?: '',
                //登陆类型 admin=总后台,member=租户后台
                'scopes' => 'member',
                'type' => 'app',//角色组类型：system=系统，app=应用，plugin=插件
                'app_name' => $name,//应用名称
            ];
            $group = AuthGroup::create($groupParam);
            if (!$group) throw new Exception("{$item['name']}角色导入失败");

            //应用模块记录
            if (!isset($miniapps[$name])) throw new Exception("{$name}应用不存在");
            $modules[] = [
                'group_id' => $group['id'],
                'miniapp_id' => $miniapps[$name],
                'name' => $item['name'],
                'type' => $item['module_type'] ?: '1',
                'price' => $item['module_price'] ?: 0,
                'desc' => $item['remark'] ?: '',
            ];

            //插入节点
            if (isset($item['nodes']) && !empty($item['nodes'])) self::importNodeData($name, $group, $item['nodes']);
        }

        if ($modules) {
            $miniappModule = new MiniappModule();
            $miniappModule->saveAll($modules);
        }

        return true;
    }

    /**
     * 导入节点
     * @param string $name
     * @return bool
     * @throws Exception
     */
    private static function importNodeData($name, $group, $nodes)
    {
        //为该应用菜单建立根节点,应用管理下,有则取出
        $rootNodeParam = [
            "pid" => 0,//上级菜单id,填0代表是顶部选项卡
            "title" => (self::getVersion($name))['title'],//菜单名称
            "app_name" => $name,//应用/插件名
            "type" => 'app',//权限分类 系统/应用/插件
            "menu_path" => $name,//后台url
            "name" => $name,//后台url
            "auth_name" => $name,//权限标识,必填
            "param" => '',//参数
            "target" => '_self',//打开方式
            "ismenu" => 1,//是否菜单
            "icon" => '',//图标
            "remark" => '',//备注
            "scopes" => 'member'//scopes
        ];;
        $rootNode = AuthNode::where($rootNodeParam)->find();
        if (!$rootNode) $rootNode = AuthNode::create($rootNodeParam);

        //把根节点导入该角色组中
        $groupNodeParam = [
            'group_id' => $group['id'],
            'node_id' => $rootNode['id'],
            'node_name' => $rootNode['name'],
            'auth_name' => $rootNode['auth_name'],
            'type' => 'app',
        ];
        AuthGroupNode::create($groupNodeParam);

        self::importNode($name, $group, $nodes, $rootNode['id']);

        return true;
    }

    /**
     * 导入节点与角色节点中间表
     * @param array $groups
     * @param array $data
     * @param int $pid 1172是应用管理的节点id
     * @return bool
     * @throws Exception
     */
    private static function importNode($name, $group, $nodes, $pid)
    {
        foreach ($nodes as $item) {
            //插入节点
            $nodeParam = [
                "pid" => $pid,//上级菜单id,填0代表是顶部选项卡
                "title" => $item['title'],//菜单名称
                "app_name" => $name,//应用/插件名
                "type" => 'app',//权限分类 系统/应用/插件
                "menu_path" => $item['menu_path'],//后台url
                "name" => $item['name'],//后台url
                "auth_name" => $item['auth_name'],//权限标识,必填
                "param" => isset($item['param']) ? $item['param'] : '',//参数
                "target" => isset($item['target']) ? $item['target'] : '_self',//打开方式
                "ismenu" => isset($item['ismenu']) ? $item['ismenu'] : 0,//是否菜单
                "icon" => isset($item['icon']) ? $item['icon'] : '',//图标
                "remark" => isset($item['remark']) ? $item['remark'] : '',//备注
                "scopes" => 'member'//scopes
            ];
            $node = AuthNode::create($nodeParam);
            if (!$node) throw new Exception("{$item['title']}节点插入失败");

            //插入节点中间表
            $groupNodeParam = [
                'group_id' => $group['id'],
                'node_id' => $node['id'],
                'node_name' => $node['name'],
                'auth_name' => $node['auth_name'],
                'type' => 'app',
            ];
            AuthGroupNode::create($groupNodeParam);

            if (isset($item['children']) && !empty($item['children'])) self::importNode($name, $group, $item['children'], $node['id']);
        }
        return true;
    }


    /**
     * 获取应用配置信息
     * @param string $dir
     * @return array|mixed
     */
    public static function getAppConfig(string $dir)
    {
        $miniapp_config = root_path() . 'app/' . $dir . '/install/bw_config.php';
        $config = [];
        if (is_file($miniapp_config)) {
            $config = include $miniapp_config;
        }
        return $config;
    }

    /**
     * 获取应用组合数据信息
     * @param string $dir
     * @return array|mixed
     */
    public static function getAppData(string $dir)
    {
        $miniapp_config = root_path() . 'app/' . $dir . '/install/bw_data.php';
        $config = [];
        if (is_file($miniapp_config)) {
            $config = include $miniapp_config;
        }
        return $config;
    }

    /**
     * 获取应用事件配置信息
     * @param string $dir
     * @return array|mixed
     */
    public static function getEventData(string $dir)
    {
        $addon_dir = 'addons';
        //批量搜索全部事件配置
        if($dir == CALL_ALL){
            $regular_addon = root_path() . "{$addon_dir}/*/install/event_ini.php";
            $regular = root_path() . "app/*/install/event_ini.php";
            $files = glob($regular);  //根据表达式匹配返回相匹配的文件列表
            $files_addon = glob($regular_addon);  //根据表达式匹配返回相匹配的文件列表
            $files = array_merge($files,$files_addon);
            $config = [];
            foreach ($files as $file)
            {
                if (is_file($file)) {
                    $config[] = include $file;
                }
            }
        }else{
            $miniapp_config = root_path() . "app/{$dir}/install/event_ini.php";
            $miniapp_addon = root_path() . "{$addon_dir}/{$dir}/install/event_ini.php";
            $config = [];
            if (is_file($miniapp_config)) {
                $config[] = include $miniapp_config;
            }
            if (is_file($miniapp_addon)) {
                $config[] = include $miniapp_addon;
            }
        }
        return $config;
    }

    /**
     * 调用应用事件
     * @param string $dir
     * @return array|mixed
     */
    public static function handle($dir, $event, $event_name)
    {
        //查询是否有应用事件
        $Event = self::getEventData($dir);
        if ($Event) {
             $list = $Event;
            foreach ($list as $event_item)
            {
                $class = null;
                $method = null;
                $scope = 'common';
                if (isset($event_item[$event_name]['class']) && $event_item[$event_name]['class']) $class = $event_item[$event_name]['class'];
                if (isset($event_item[$event_name]['method']) && $event_item[$event_name]['method']) $method = $event_item[$event_name]['method'];
                if (isset($event_item[$event_name]['scope']) && $event_item[$event_name]['scope'] && in_array($event_item[$event_name]['scope'], ['static', 'object', 'common'])) $scope = $event_item[$event_name]['scope'];
                if ($method) {
                    //方法调用
                    switch ($scope) {
                        case "common":  //公共函数
                            $method($event);
                            break;
                        case "static":  //类静态函数
                            if ($class) {
                                $class::$method($event);
                            }
                            break;
                        case "object":  //类对象函数
                            if ($class) {
                                (new $class)->$method($event);
                            }
                            break;
                    }
                }
            }
        }

    }

    /**
     * 安装应用配置和租户数据
     * @param string $dir
     * @return array|mixed
     */
    public static function installConfigAndGroupData($dir, $trans = false)
    {
        $config = self::getAppConfig($dir);//配置
        $config_data = self::getAppData($dir);  //组合数据
        $config_insert_list = [];//添加的配置数据
        $group_data_insert_list = [];//添加的组合数据
        $config_tabs = [];
        $group_names = [];
        $member_ids = Member::where('parent_id', 0)->column('id');//所有顶级租户
        //配置
        if ($config) {
            if (isset($config['config_name']) && $config['config_name']) {
                $config_names = $config['config_name'];
                //检查配置分类是否有缺少
                $config_tabs = array_keys($config_names); //得到所有配置分类
                $config_tab_ids = ConfigTab::where('tab_name', 'in', $config_tabs)->where('dir', 'null')->where('scopes', 'member')->column('id', 'tab_name');
                if (count($config_tabs) != count($config_tab_ids)) throw new Exception("与安装期望参数不符:在bw_config.php中配置分类数量与实际安装参数不一致");
                //遍历配置分类，拼装配置数据
                foreach ($config_tab_ids as $tab_name => $tab_id) {
                    if (!$config_names[$tab_name]) throw new Exception("与安装期望参数不符:未在bw_config.php中{$tab_name}配置分类下写入配置");
                    //得到分类初始化配置
                    $config_list = Config::where('member_id', 0)->where('dir', 'null')->where('scopes', 'member')->where('config_name', 'in', $config_names[$tab_name])->select()->toArray();
                    if (count($config_names[$tab_name]) != count($config_list)) throw new Exception("与安装期望参数不符:在bw_config.php中{$tab_name}配置分类下实际配置数量与实际安装参数不一致");
                    //组装插入数据
                    foreach ($config_list as $config_init) {
                        unset($config_init['id']);
                        $config_init['member_id'] = 0;
                        $config_init['tab_id'] = $tab_id;
                        $config_init['dir'] = $dir;
                        //插入初始化数据
                        $config_insert_list[] = $config_init;
                        //插入租户配置
                        foreach ($member_ids as $member_id) {
                            $config_init['member_id'] = $member_id;
                            $config_insert_list[] = $config_init;
                        }
                    }
                }

            }
        }

        //组合数据
        if ($config_data) {
            if (isset($config_data['config_name']) && $config_data['config_name']) {
                $group_names = $config_data['config_name'];
                $group_ids = ConfigGroup::where('config_name', 'in', $group_names)->where('dir', 'null')->where('scopes', 'member')->column('id', 'config_name');
                if (count($group_names) != count($group_ids)) throw new Exception("与安装期望参数不符:在bw_data.php中组合数据数量与实际安装参数不一致");
                //遍历配置分类，拼装配置数据
                foreach ($group_ids as $config_name => $group_id) {
                    //得到组合数据初始化配置
                    $group_data_list = ConfigGroupData::where('member_id', 0)->where('dir', 'null')->where('scopes', 'member')->where('config_name', '=', $config_name)->select()->toArray();
                    //组装插入数据
                    foreach ($group_data_list as $group_data) {
                        unset($group_data['id']);
                        $group_data['member_id'] = 0;
                        $group_data['group_id'] = $group_id;
                        $group_data['dir'] = $dir;
                        //插入初始化数据
                        $group_data_insert_list[] = $group_data;
                        //插入租户配置
                        foreach ($member_ids as $member_id) {
                            $group_data['member_id'] = $member_id;
                            $group_data_insert_list[] = $group_data;
                        }
                    }
                }
            }
        }
        if ($trans) {
            Db::startTrans();
        }

        $res = $res1 = $res2 = $res3 = $res4 = true;
        try {

            if ($config_insert_list) {
                if ($config_tabs) {
                    //删除配置，删除初始化配置
                    Config::where('tab_name', 'in', $config_tabs)->where('dir', $dir)->delete();
                    Config::where('tab_name', 'in', $config_tabs)->where('dir', 'null')->delete();
                }

                //添加配置
                $configModel = new Config;
                $res2 = $configModel->saveAll($config_insert_list);
                if ($config_tabs) {

                    //更新配置分类dir
                    ConfigTab::where('tab_name', 'in', $config_tabs)->where('dir', 'null')->update(['dir' => $dir]);
                    Config::where('tab_name', 'in', $config_tabs)->where('dir', 'null')->update(['dir' => $dir]);
                }
            }

            if ($group_data_insert_list) {
                if ($group_names) {

                    //删除组合数据，删除组合数据
                    ConfigGroupData::where('config_name', 'in', $group_names)->where('dir', $dir)->delete();
                    ConfigGroupData::where('config_name', 'in', $group_names)->where('dir', 'null')->delete();
                }

                //添加组合数据
                $groupDataModel = new ConfigGroupData;
                $res4 = $groupDataModel->saveAll($group_data_insert_list);
                if ($group_names) {
                    //更新数据组dir
                    ConfigGroup::where('config_name', 'in', $group_names)->where('dir', 'null')->update(['dir' => $dir]);
                    ConfigGroupData::where('config_name', 'in', $group_names)->where('dir', 'null')->update(['dir' => $dir]);

                }
            }

            $res = $res && $res1 && $res2 && $res3 && $res4 && true;

            if ($res) {
                if ($trans) {
                    Db::commit();
                }
            } else {
                if ($trans) {
                    Db::rollback();
                }
            }
        } catch (\Exception $e) {
            if ($trans) {
                Db::rollback();
            }
            throw new Exception($e->getMessage());
        }
        if (!$res) throw new Exception("安装配置和组合数据失败");


        return $res;
    }

    /**
     * 导出应用用户组数据至文件
     * @param string $dir 应用目录名
     */
    public static function exportMiniappGroup($dir)
    {
        //应用
        $miniapp = Miniapp::where('dir', $dir)->find();
        if (!$miniapp) return true;

        //功能模块
        $miniapp_modules = MiniappModule::alias('miniapp_module')
            ->where('miniapp_module.miniapp_id', $miniapp['id'])
            ->join('auth_group', 'miniapp_module.group_id = auth_group.id')
            ->field('auth_group.id,miniapp_module.group_id,miniapp_module.miniapp_id, miniapp_module.name, auth_group.group_name, miniapp_module.desc remark, miniapp_module.type module_type, miniapp_module.price module_price')
            ->order(['miniapp_module.type' => 'asc', 'miniapp_module.id' => 'asc'])
            ->select();
        if (!$miniapp_modules) return true;
        $miniapp_modules = $miniapp_modules->toArray();

        //应用根节点
        $root_node = AuthNode::where('pid', 0)->where('app_name', $dir)->where('type', 'app')->field('id, pid, title')->find();
        if (!$root_node) return true;

        foreach ($miniapp_modules as &$miniapp_module) {
            $auth_group = [];
            $nodes = AuthNode::alias('node')
                ->join('auth_group_node', 'node.id = auth_group_node.node_id')
                ->where([
                    ['auth_group_node.group_id', '=', $miniapp_module['group_id']],
                    ['node.pid', '<>', 0]
                ])
                ->field('node.id, node.pid, node.title, node.menu_path, node.name, node.auth_name, node.ismenu, node.param, node.target, node.icon, node.remark')
                ->order('node.sort desc, node.id asc')
                ->select();
            $nodes && $nodes = deal_list_to_tree($nodes->toArray(), 'id', 'pid', 'children', false, $root_node['id']);
            $miniapp_module['nodes'] = $nodes;
        }

        file_put_contents('group.php', '<?php' . "\n\nreturn " . var_export($miniapp_modules, true) . ";");
        return true;
    }

    /**
     * 导出应用安装的配置和数据组文件
     */
    public static function exportMiniappConfig($dir, $dir_path, $un_dir_path)
    {

        //应用
        $miniapp = Miniapp::where('dir', $dir)->find();
        if (!$miniapp) return true;
        $bw_config = ['config_name' => []];
        $bw_data = ['config_name' => []];
        $config_list = [];

        $config_tab_sql = '';//配置分类安装sql
        $config_sql = '';  //配置安装sql
        $group_sql = ''; //数据组安装sql
        $group_data_sql = ''; //数据组数据安装sql
        $install_sql = '';

        $config_tab_delete_sql = ''; //配置分类卸载sql
        $config_delete_sql = '';  //配置卸载sql
        $group_delete_sql = '';  //数据组卸载sql
        $group_data_delete_sql = '';  //数据组数据卸载sql
        $uninstall_sql = '';

        $tableSql = self::getInstallSql($dir);


        //查询应用配置
        //查询应用配置分类
        $configTabs = ConfigTab::where('dir', $dir)->where('scopes', 'member')->column('tab_name');
        $configTabList = ConfigTab::where('dir', $dir)->where('scopes', 'member')->select()->toArray();
        if ($configTabs) {
            foreach ($configTabs as $configTab) {
                $config_list[$configTab] = Config::where('dir', $dir)->where('member_id', 0)->where('scopes', 'member')->where('tab_name', $configTab)->column('config_name');
            }
            if ($config_list) $bw_config['config_name'] = $config_list;
            foreach ($configTabList as $tab) {
                unset($tab['id']);
                unset($tab['dir']);
                //得到配置分类插入sql
                $insertSQL = ConfigTab::fetchSql(true)->save($tab);
                $config_tab_sql .= $insertSQL . ';' . PHP_EOL;
                //得到配置分类卸载sql
                $deleteSQL = ConfigTab::fetchSql(true)->where('tab_name', $tab['tab_name'])->where('dir', $dir)->delete();
                $config_tab_delete_sql .= $deleteSQL . ';' . PHP_EOL;
                //得到配置卸载sql
                $deleteSQL = Config::fetchSql(true)->where('tab_name', $tab['tab_name'])->where('dir', $dir)->delete();
                $config_delete_sql .= $deleteSQL . ';' . PHP_EOL;
                $config_list[$configTab] = Config::where('dir', $dir)->where('member_id', 0)->where('scopes', 'member')->where('tab_name', $configTab)->column('config_name');
            }
            //查询所有初始化配置
            $configs = Config::where('dir', $dir)->where('member_id', 0)->where('scopes', 'member')->where('tab_name', 'in', $configTabs)->select()->toArray();
            foreach ($configs as $config) {
                //得到配置值插入sql
                unset($config['id']);
                unset($config['dir']);
                $insertSQL = Config::fetchSql(true)->save($config);
                $config_sql .= $insertSQL . ';' . PHP_EOL;
            }
        }
        //生成bwdata
        $configGroups = ConfigGroup::where('dir', $dir)->where('scopes', 'member')->column('config_name');
        $configGroupList = ConfigGroup::where('dir', $dir)->where('scopes', 'member')->select()->toArray();
        foreach ($configGroupList as $configGroup) {
            //得到数据组插入sql
            unset($configGroup['id']);
            unset($configGroup['dir']);
            $insertSQL = ConfigGroup::fetchSql(true)->save($configGroup);
            $group_sql .= $insertSQL . ';' . PHP_EOL;

            //得到数据组卸载sql
            $deleteSQL = ConfigGroup::fetchSql(true)->where('config_name', $configGroup['config_name'])->where('dir', $dir)->delete();
            $group_delete_sql .= $deleteSQL . ';' . PHP_EOL;

            //得到数据组数据卸载sql
            $deleteSQL = ConfigGroupData::fetchSql(true)->where('config_name', $configGroup['config_name'])->where('dir', $dir)->delete();
            $group_data_delete_sql .= $deleteSQL . ';' . PHP_EOL;

        }
        //查询所有组合数据的初始化数据
        $configGroupDatas = ConfigGroupData::where('dir', $dir)->where('member_id', 0)->where('scopes', 'member')->where('config_name', 'in', $configGroups)->select()->toArray();
        foreach ($configGroupDatas as $configGroupData) {
            //得到数据组插入sql
            unset($configGroupData['id']);
            unset($configGroupData['dir']);
            $insertSQL = ConfigGroupData::fetchSql(true)->save($configGroupData);
            $group_data_sql .= $insertSQL . ';' . PHP_EOL;

        }


        if ($configGroups) $bw_data['config_name'] = $configGroups;
        if ($bw_config) file_put_contents($dir_path . 'bw_config.php', '<?php' . "\n\nreturn " . var_export($bw_config, true) . ";");
        if ($bw_data) file_put_contents($dir_path . 'bw_data.php', '<?php' . "\n\nreturn " . var_export($bw_data, true) . ";");

        //组装表安装sql
        if ($tableSql['install']) $install_sql .= PHP_EOL . $tableSql['install'] . PHP_EOL;
        //组装配置的安装sql
        if ($config_tab_sql) $install_sql .= PHP_EOL . $config_tab_sql . PHP_EOL;
        if ($config_sql) $install_sql .= PHP_EOL . $config_sql . PHP_EOL;
        if ($group_sql) $install_sql .= PHP_EOL . $group_sql . PHP_EOL;
        if ($group_data_sql) $install_sql .= PHP_EOL . $group_data_sql . PHP_EOL;
        //生成配置插入语句
        if ($install_sql) file_put_contents($dir_path . 'install.sql', $install_sql);

        //组装表卸载sql
        if ($tableSql['uninstall']) $uninstall_sql .= PHP_EOL . $tableSql['uninstall'] . PHP_EOL;
        //组装配置的卸载sql
        if ($config_tab_delete_sql) $uninstall_sql .= PHP_EOL . $config_tab_delete_sql . PHP_EOL;
        if ($config_delete_sql) $uninstall_sql .= PHP_EOL . $config_delete_sql . PHP_EOL;
        if ($group_delete_sql) $uninstall_sql .= PHP_EOL . $group_delete_sql . PHP_EOL;
        if ($group_data_delete_sql) $uninstall_sql .= PHP_EOL . $group_data_delete_sql . PHP_EOL;
        //生成配置卸载sql
        if ($uninstall_sql) file_put_contents($un_dir_path . 'uninstall.sql', $uninstall_sql);

    }


    /**
     * 迁移应用静态文件
     */
    public static function exportMiniappStatic($dir, $target_dir = '')
    {
        //1.拷贝静态资源文件
        if (file_exists($dir)) {
            //拷贝模板到前台模板目录中去 public/static/demoApp/
            File::copy_dir($dir, $target_dir);
        }
    }


    /**
     * 迁移应用事件文件
     */
    public static function exportMiniappEventInit($target_dir = 'event_ini.php')
    {
        $event_ini = file_get_contents(root_path() . DS . 'buwang' . DS . 'template' . DS . 'event_ini.template.php');
        file_put_contents($target_dir, $event_ini);
    }


    /**
     * 导出应用用户组数据至文件
     * @param string $dir 应用目录名
     */
    public static function exportMiniappMenu($dir, $target_dir = 'group.php')
    {
        //应用
        $miniapp = Miniapp::where('dir', $dir)->find();
        if (!$miniapp) return true;

        //功能模块
        $miniapp_modules = MiniappModule::alias('miniapp_module')
            ->where('miniapp_module.miniapp_id', $miniapp['id'])
            ->join('auth_group', 'miniapp_module.group_id = auth_group.id')
            ->field('auth_group.id,miniapp_module.group_id,miniapp_module.miniapp_id, miniapp_module.name, auth_group.group_name, miniapp_module.desc remark, miniapp_module.type module_type, miniapp_module.price module_price')
            ->order(['miniapp_module.type' => 'asc', 'miniapp_module.id' => 'asc'])
            ->select();
        if (!$miniapp_modules) return true;
        $miniapp_modules = $miniapp_modules->toArray();

        //应用根节点
        $root_node = AuthNode::where('pid', 0)->where('app_name', $dir)->where('type', 'app')->field('id, pid, title')->find();
        if (!$root_node) return true;

        foreach ($miniapp_modules as &$miniapp_module) {
            $auth_group = [];
            $nodes = AuthNode::alias('node')
                ->join('auth_group_node', 'node.id = auth_group_node.node_id')
                ->where([
                    ['auth_group_node.group_id', '=', $miniapp_module['group_id']],
                    ['node.pid', '<>', 0]
                ])
                ->field('node.id, node.pid, node.title, node.menu_path, node.name, node.auth_name, node.ismenu, node.param, node.target, node.icon, node.remark')
                ->order('node.sort desc, node.id asc')
                ->select();
            $nodes && $nodes = deal_list_to_tree($nodes->toArray(), 'id', 'pid', 'children', false, $root_node['id']);
            $miniapp_module['nodes'] = $nodes;
        }

        file_put_contents($target_dir, '<?php' . "\n\nreturn " . var_export($miniapp_modules, true) . ";");
        return true;
    }


    /**导出安装目录
     * @param $dir
     * @param string $target_dir
     */
    public static function exportPath($dir)
    {
        $dir_path = root_path() . 'public' . DS . $dir . '/install/';
        $un_dir_path = root_path() . 'public' . DS . $dir . '/uninstall/';
        $target_dir = root_path() . 'public' . DS . $dir . DS . 'install' . DS . 'static' . DS;
        return [
            'static' => $target_dir . '*',
            'group' => $dir_path . 'group.php',
            'event' => $dir_path . 'event_ini.php',
            'bw_config' => $dir_path . 'bw_config.php',
            'bw_data' => $dir_path . 'bw_data.php',
            'install' => $dir_path . 'install.sql',
            'uninstall' => $un_dir_path . 'uninstall.sql',
        ];
    }


    /**导出安装目录
     * @param $dir
     * @param string $target_dir
     */
    public static function exportInstallDir($dir)
    {
        $dir_path = root_path() . 'public' . DS . $dir . '/install';
        @mkdir($dir_path, 0777, true);
        $dir_path .= '/';

        $un_dir_path = root_path() . 'public' . DS . $dir . '/uninstall';
        @mkdir($un_dir_path, 0777, true);
        $un_dir_path .= '/';

        $sorce_dir = root_path() . 'public' . DS . 'static' . DS . strtolower($dir) . DS;
        $target_dir = root_path() . 'public' . DS . $dir . DS . 'install' . DS . 'static' . DS;
        $event_ini = $dir_path . 'event_ini.php';
        $menu = $dir_path . 'group.php';
        self::exportMiniappConfig($dir, $dir_path, $un_dir_path);//导入配置文件
        self::exportMiniappStatic($sorce_dir, $target_dir);//导入静态文件
        self::exportMiniappStatic($sorce_dir, $target_dir);//导入静态文件
        self::exportMiniappEventInit($event_ini);//导入钩子配置文件
        self::exportMiniappMenu($dir, $menu);//导入菜单配置
    }

    /**得到所有表名
     * @param $database
     * @param string $dir
     */
    public static function list_tables($dir = '')
    {
        $tables = Db::query("SHOW TABLES FROM  bwmall");
        //数据库中有哪些表

        $tabList = array();
        foreach ($tables as $table) {
            $table_name = $table['Tables_in_bwmall'];
            if ($dir) {
                if (preg_match("/^bw_" . $dir . "+/", $table_name)) {
                    $tabList[] = $table_name;
                }
            } else {
                $tabList[] = $table_name;
            }
        }
        return $tabList;
    }

    /**得到安装sql和删除sql
     * @param string $dir
     */
    public static function getInstallSql($dir = '')
    {
        $install_list = '';
        $uninstall_list = '';
        //得到表名
        $tableNames = self::list_tables($dir);
        foreach ($tableNames as $tableName) {
            $tables = Db::query("show create table {$tableName}");
            //得到表结构
            if (isset($tables[0]['Create Table'])) {
                $info = "-- ----------------------------" . PHP_EOL;
                $info .= "-- Table structure for `{$tableName}`" . PHP_EOL;
                $info .= "-- ----------------------------" . PHP_EOL;
                $info .= "DROP TABLE IF EXISTS `{$tableName}` ;" . PHP_EOL;
                $sqlStr = $info . $tables[0]['Create Table'] . ";\r\n\r\n";
                $install_list .= PHP_EOL . $sqlStr . PHP_EOL;
                $uninstall_list .= PHP_EOL . "DROP TABLE IF EXISTS `{$tableName}` ;" . PHP_EOL;
            }
        }
        return [
            'install' => $install_list,
            'uninstall' => $uninstall_list,
        ];
    }


    /**
     * 导出对应角色的安装group.php
     * @param string $dir 表前缀
     */
    public static function exportGroup($miniapp_modules, $target_dir = 'group.php')
    {
        foreach ($miniapp_modules as &$miniapp_module) {
            $auth_group = [];
            $nodes = AuthNode::alias('node')
                ->join('auth_group_node', 'node.id = auth_group_node.node_id')
                ->where([
                    ['auth_group_node.group_id', '=', $miniapp_module['group_id']],
                    ['node.pid', '<>', 0]
                ])
                ->field('node.id, node.pid, node.title, node.menu_path, node.name, node.auth_name, node.ismenu, node.param, node.target, node.icon, node.remark')
                ->order('node.sort desc, node.id asc')
                ->select();
            $nodes && $nodes = deal_list_to_tree($nodes->toArray(), 'id', 'pid', 'children', false, $miniapp_module['root_id']);
            $miniapp_module['nodes'] = $nodes;
        }
        file_put_contents($target_dir, '<?php' . "\n\nreturn " . var_export($miniapp_modules, true) . ";");
        return true;
    }


}